/*
 *
 * Copyright(c) 2010 Safelayer Secure Communications
 *
 * This is open source software, placed under the terms of the
 * GNU GENERAL PUBLIC LICENSE Version 3.
 * Please see: http://www.gnu.org/licenses/gpl-3.0.txt
 * 
 * Permission to use, copy, modify, and distribute this software
 * for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that
 * both the copyright notice and this permission notice appear in
 * supporting documentation.
 * 
 */
package com.neuralnoise.foaf.core;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.RDF;

/**
 * Utility class which provides methods for search FOAF person representations into a RDF graph.
 * 
 * @author victor.martinez <swtp@safelayer.com>
 * 
 */
public class SearchUtils {

	/**
	 * Attempts to find the primary person within a RDF graph. To 'helps' this method to find the
	 * primaryPerson the <code>uri</code> parameter can be treated as a primary person identifier or
	 * as a personal profile document . In any case empty values for <code>uri</code> parameter are
	 * allowed , trying to 'guess' which is the primary person using an internal algorithm without
	 * guarantees of success search.
	 * 
	 * 
	 * @param model The model to search the primary person.
	 * @param uri The uri identifier o empty.
	 * @return The primary person as resource.
	 */
	public static Resource searchPrimaryPerson(Model model, String uri) {
		// attempts to find a labeled Person
		Resource primary = findPersonById(model, uri);

		if (isFoafPersonResource(primary))
			return primary;

		// attempts to find a person labeled as creator
		// within a Personal Profile document
		primary = findPersonByCreator(model, uri);
		if (primary != null && isFoafPersonResource(primary))
			return primary;

		// attempts to find a person labeled as primaryTopic
		// within a Personal Profile document
		primary = findPersonByPrimaryTopic(model, uri);

		if (primary != null)
			return primary;

		// attempts to find a person labeled as maker
		// within a Personal Profile document
		primary = findPersonByMaker(model, uri);
		if (primary != null && isFoafPersonResource(primary))
			return primary;

		// attempts to find a person through his contacts
		primary = searchPrimaryPersonByContacts(model);
		if (primary != null)
			return primary;

		// last alternative
		primary = searchFirstPerson(model);
		if (primary != null)
			return primary;

		return null;
	}

	// Helpers -----------------------------------------------------------------------------------

	/**
	 * Find the primary person through the Personal Profile document labeled as <code>maker</code>.
	 * If exist return the primary person else return <tt>null</tt>.
	 * 
	 * @param model The model containing or not the primary person.
	 * @param documentUri The uri of the person profile document.
	 * @return the primary person labeled as maker , <tt>null</tt> otherwise.
	 */
	private static Resource findPersonByMaker(Model model, String documentUri) {
		Resource personalProfileDocument = model.getResource(documentUri);
		Resource primaryTopic = null;
		if (personalProfileDocument.hasProperty(FOAFVocabulary.maker)) {
			primaryTopic = (Resource) personalProfileDocument.getProperty(FOAFVocabulary.maker).getObject();
		}
		return primaryTopic;
	}

	/**
	 * Find the primary person through the Personal Profile document labeled as
	 * <code>primayTopic</code>. If exist return the primary person else return <tt>null</tt>.
	 * 
	 * @param model The model containing or not the primary person.
	 * @param documentUri The uri of the person profile document.
	 * @return the primary person labeled as primaryTopic , <tt>null</tt> otherwise.
	 */
	private static Resource findPersonByPrimaryTopic(Model model, String documentUri) {
		Resource personalProfileDocument = model.getResource(documentUri);
		Resource primaryTopic = null;
		if (personalProfileDocument.hasProperty(FOAFVocabulary.primaryTopic)) {
			primaryTopic = (Resource) personalProfileDocument.getProperty(FOAFVocabulary.primaryTopic).getObject();
		}
		return primaryTopic;
	}

	/**
	 * Find the primary person through the Personal Profile document labeled as <code>creator</code>
	 * . If exist return the primary person else return <tt>null</tt>.
	 * 
	 * @param model The model containing or not the primary person.
	 * @param documentUri The uri of the person profile document.
	 * @return the primary person labeled as creator , <tt>null</tt> otherwise.
	 */
	private static Resource findPersonByCreator(Model model, String documentUri) {
		Resource personalProfileDocument = model.getResource(documentUri);
		Resource primaryTopic = null;
		if (personalProfileDocument.hasProperty(DC.creator)) {
			primaryTopic = (Resource) personalProfileDocument.getProperty(DC.creator).getObject();
		}
		return primaryTopic;
	}

	/**
	 * Find the primary person with the given uri. If exist return the primary person else return
	 * <tt>null</tt>.
	 * 
	 * @param model The model containing or not the primary person.
	 * @param uri The uri of the primary person to find.
	 * @return the primary person with the given uri , <tt>null</tt> otherwise.
	 */
	private static Resource findPersonById(Model model, String uri) {
		Resource primaryPerson = model.getResource(uri);
		if (primaryPerson.isAnon())
			return null;

		return primaryPerson;
	}

	/**
	 * Select as primary person of a the given model the foaf:Person which more foaf:knows
	 * relationships contains, since a primary person describe all his contacts (foaf:knows
	 * relationships) in a RDF graph.
	 * 
	 * @param model The model containing or not the primary person.
	 * @return the primary person with more foaf:knows relationships , <tt>null</tt> otherwise.
	 */
	private static Resource searchPrimaryPersonByContacts(Model model) {
		ResIterator people = model.listResourcesWithProperty(RDF.type, FOAFVocabulary.Person);
		Resource primaryPerson = null;
		int maxNumberOfContacts = 0;
		int currentNumberOfContacts;
		while (people.hasNext()) {
			Resource candidate = people.nextResource();
			NodeIterator contacts = model.listObjectsOfProperty(candidate, FOAFVocabulary.knows);
			currentNumberOfContacts = contacts.toList().size();
			if (currentNumberOfContacts >= maxNumberOfContacts) {
				maxNumberOfContacts = currentNumberOfContacts;
				primaryPerson = candidate;
			}
		}
		return primaryPerson;
	}

	/**
	 * Search the first one FOAF person within a RDF graph.
	 * 
	 * @param model The model containing or not the primary person.
	 * @return The first one FOAF person.
	 */
	private static Resource searchFirstPerson(Model model) {
		ResIterator people = model.listResourcesWithProperty(RDF.type, FOAFVocabulary.Person);
		if (people.hasNext())
			return people.nextResource();
		return null;
	}

	private static boolean isFoafPersonResource(Resource resource) {
		if (resource == null)
			return false;
		return resource.hasProperty(RDF.type, FOAFVocabulary.Person);
	}
}